#ifndef CONF_CONNECTION_H_
#define CONF_CONNECTION_H_

#include <gtkmm.h>
#include <boost/ptr_container/ptr_list.hpp>
#include <boost/utility.hpp>

#include "../log.h"

namespace gpyp {
namespace conf {

class PropertyConnection : boost::noncopyable {
private:
  sigc::signal<void, PropertyConnection &> signal_changed_;
  sigc::signal<void, PropertyConnection &> signal_destroy_;
public:
  inline sigc::signal<void, PropertyConnection &> signal_changed() {
    return signal_changed_;
  }
  inline sigc::signal<void, PropertyConnection &> signal_destroy() {
    return signal_destroy_;
  }

private:
  GObject      *obj_;
  Glib::ustring name_;
  bool          writable_;
  GType         value_type_;

  gulong        connection_notify_;
  gulong        connection_signal_;
  gulong        connection_destroy_;

  bool          unref_;

  static void static_on_notify(GObject    *gobject,
                               GParamSpec *pspec,
                               gpointer    user_data);
  static void static_on_changed(GtkTextBuffer *textbuffer,
                                gpointer       user_data);
  static void static_on_destroy(GtkObject *gobject,
                                gpointer   user_data);

  void on_changed();
  void on_destroy();

  inline void disconnect(gulong PropertyConnection::*ptr) {
    if (obj_ && this->*ptr) {
      g_signal_handler_disconnect(obj_, this->*ptr);
    }
    this->*ptr = 0;
  }

protected:
  template<typename T>
  bool on_set_value(const T &/*value*/) {
    return false;
  }

#define ON_SET_VALUE_INT(TYPE, GTYPE)                         \
  bool on_set_value(TYPE value) {                             \
    switch(value_type_) {                                     \
    case G_TYPE_INT: case G_TYPE_UINT:                        \
    case G_TYPE_LONG: case G_TYPE_ULONG:                      \
    case G_TYPE_INT64: case G_TYPE_UINT64:                    \
    case G_TYPE_FLOAT: case G_TYPE_DOUBLE:                    \
      {                                                       \
        GValue v;                                             \
        memset(&v, 0, sizeof(v));                             \
        g_value_init(&v, GTYPE);                              \
        g_value_set_##TYPE(&v, value);                        \
        g_object_set_property(obj_, name_.c_str(), &v);       \
        return true;                                          \
      }                                                       \
    }                                                         \
    return false;                                             \
  }
  ON_SET_VALUE_INT(int,    G_TYPE_INT);
  ON_SET_VALUE_INT(uint,   G_TYPE_UINT);
  ON_SET_VALUE_INT(double, G_TYPE_DOUBLE);
#undef ON_SET_VALUE_INT

  bool on_set_value(bool value) {
    if (value_type_ == G_TYPE_BOOLEAN) {
      g_object_set(obj_, name_.c_str(), value ? TRUE : FALSE, NULL);
      return true;
    }
    return false;
  }
  bool on_set_value(const char *value) {
    if (value_type_ == G_TYPE_STRING) {
      g_object_set(obj_, name_.c_str(), value, NULL);
      return true;
    }
    return false;
  }
  bool on_set_value(const Glib::ustring &value) {
    return on_set_value(value.c_str());
  }

public:
  inline void get_value(GValue &value) const {
    g_object_get_property(G_OBJECT(obj_), name_.c_str(), &value);
  }

  template<typename T>
  void set_value(const T &value) {
    if (writable_) {
      g_signal_handler_block(obj_, connection_notify_);
      bool res = on_set_value(value);
      g_signal_handler_unblock(obj_, connection_notify_);
      if (!res) {
        log::dbg << "PropertyConnection: failed to set value" << log::endl;
      }
    }
  }

  inline bool equal(GObject *obj, const Glib::ustring &name) {
    return obj_ == obj && name_ == name;
  }

public:
  PropertyConnection(GObject *obj, const char *name);
  PropertyConnection(GtkTextBuffer *textbuffer);
  virtual ~PropertyConnection();
};

template<typename ValueType>
class ConnectionList : boost::noncopyable {
private:
  sigc::signal<void, ValueType> signal_changed_;
public:
  inline sigc::signal<void, ValueType> signal_changed() {
    return signal_changed_;
  }

private:
  boost::ptr_list<PropertyConnection> conns_;

  void conn_on_changed(PropertyConnection &conn);
  void conn_on_destroy(PropertyConnection &conn) {
    boost::ptr_list<PropertyConnection>::iterator iter;
    for(iter = conns_.begin(); iter != conns_.end(); ++iter) {
      if (&*iter == &conn) {
        conns_.erase(iter);
        return;
      }
    }
  }

  inline void add(PropertyConnection *conn) {
    conn->signal_changed()
      .connect(sigc::mem_fun(*this, &ConnectionList::conn_on_changed));
    conn->signal_destroy()
      .connect(sigc::mem_fun(*this, &ConnectionList::conn_on_destroy));
    conns_.push_back(conn);
  }
  inline void add(GObject *obj, const char *name) {
    add(new PropertyConnection(obj, name));
  }
  inline void remove(GObject *obj, const char *name) {
    boost::ptr_list<PropertyConnection>::iterator iter;
    for(iter = conns_.begin(); iter != conns_.end(); ++iter) {
      if (iter->equal(obj, name)) {
        conns_.erase(iter);
        return;
      }
    }
  }

public:
  void attach(Glib::PropertyProxy_Base prop) {
    add(prop.get_object()->gobj(), prop.get_name());
  }
  template<typename T>
  void attach(T *obj, const char *prop) {
    add(G_OBJECT(obj), prop);
  }
  void attach(Glib::RefPtr<Gtk::TextBuffer> textbuffer);

  void detach(Glib::PropertyProxy_Base prop) {
    remove(prop.get_object()->gobj(), prop.get_name());
  }
  template<typename T>
  void detach(T *obj, const char *prop) {
    remove(G_OBJECT(obj), prop);
  }
  void detach(Glib::RefPtr<Gtk::TextBuffer> textbuffer);

  template<typename T>
  void set_value(const T &value) {
    boost::ptr_list<PropertyConnection>::iterator iter;
    for(iter = conns_.begin(); iter != conns_.end(); ++iter) {
      iter->set_value(value);
    }
  }

  ConnectionList() {
  }
};

template<> inline void ConnectionList<Glib::ustring>::attach(Glib::RefPtr<Gtk::TextBuffer> textbuffer) {
  add(new PropertyConnection(textbuffer->gobj()));
}
template<> inline void ConnectionList<Glib::ustring>::detach(Glib::RefPtr<Gtk::TextBuffer> textbuffer) {
  remove(G_OBJECT(textbuffer->gobj()), "text");
}

#define CONNECTIONLIST_ON_CHANGED(TYPE, G_TYPE, G_FUNC)                 \
  template<> inline void                                                \
  ConnectionList<TYPE>::conn_on_changed(PropertyConnection &conn) {     \
    GValue gvalue;                                                      \
    memset(&gvalue, 0, sizeof(gvalue));                                 \
    g_value_init(&gvalue, G_TYPE);                                      \
    conn.get_value(gvalue);                                             \
    TYPE value = g_value_get_##G_FUNC(&gvalue);                         \
    boost::ptr_list<PropertyConnection>::iterator iter;                 \
    for(iter = conns_.begin(); iter != conns_.end(); ++iter) {          \
      if (&conn != &*iter) iter->set_value(value);                      \
    }                                                                   \
    signal_changed_(value);                                             \
  }
CONNECTIONLIST_ON_CHANGED(int,           G_TYPE_INT, int);
CONNECTIONLIST_ON_CHANGED(uint,          G_TYPE_UINT, uint);
CONNECTIONLIST_ON_CHANGED(double,        G_TYPE_DOUBLE, double);
CONNECTIONLIST_ON_CHANGED(bool,          G_TYPE_BOOLEAN, boolean);
CONNECTIONLIST_ON_CHANGED(Glib::ustring, G_TYPE_STRING, string);
#undef CONNECTIONLIST_ON_CHANGED

}/*conf*/
}/*gpyp*/

#endif/*CONF_CONNECTION_H_*/
